#include <stdlib.h>
#include <stdint.h>

#include <common/macro/build.h>
#include <common/arity.h>
#include <common/datatype.h>
#include <common/predicate.h>

/*
 * Internal structures helping to arrange the runtime array info.
 */
struct dt_args_t {
	datatype_t *datatype;
	void *child_dt_args;
};

struct pred_args_t {
	predicate_t *predicate;
	void *child_pred_args;
};

/*
 * Array format:
 * 4B number of items in array, {sizeof(size_t), size of following item, item itself} * N
 */

API_FILELOCAL void *dt_arguments_parse(char **argv, size_t argc) {
	struct dt_args_t *dt_args;
	datatype_t *datatype;
	void *child_dt_args;

	/* 1st arg: datatype, rest: args for datatype. */
	datatype = datatype_lookup(argv[0]);
	if (datatype == NULL)
		return NULL;
	
	if (datatype->args_parse != NULL) {
		child_dt_args = datatype->args_parse(&argv[1], argc - 1);
		if (child_dt_args == NULL)
			return NULL;
	} else {
		child_dt_args = NULL;
	}

	dt_args = malloc(sizeof(*dt_args));
	dt_args->datatype = datatype;
	dt_args->child_dt_args = child_dt_args;

	return dt_args;
}

API_FILELOCAL void dt_arguments_destroy(void *arguments) {
	free(arguments);
}

API_FILELOCAL int verify(void *datatype_args, char *data, size_t size) {
	uint32_t i, arr_items;
	size_t arr_size;
	char *ptr;

	/*
	 * Verify that the size reported by application conforms
	 * with the internal array structure.
	 *
	 * Also call verify for each given datum.
	 */
	ptr = data + sizeof(arr_items);

	arr_size = 0;
	arr_items = *((uint32_t*)data);
	for (i = 0; i < arr_items; i++) {
		arr_size += *((size_t*)ptr);

		if (arr_size > size)
			return 0;

		ptr += arr_size;

		/* TODO, verify for each given datum. */
	}

	if (size != arr_size)
		return 0;
	return 1;
}

API_FILELOCAL void *predicate_nary_compile(void *datatype_args, size_t *argsizes, char **arguments, int argc) {
	struct pred_args_t *pred_args;
	datatype_t *datatype;
	predicate_t *predicate;
	void *child_pred_args;

	/* 1. Get the datatype. */
	datatype = ((struct dt_args_t*)datatype_args)->datatype;

	/* 2. Look up the predicate in arg 0. If it's not present, fail. */
	predicate = datatype_predicate_lookup(datatype, arguments[0]);
	if (predicate == NULL)
		return NULL;

	/* 3. Let the datatype's predicate take care of the arguments. */
	if (!arity_check(predicate->minargs, predicate->maxargs, argc - 1))
		return NULL;

	if (predicate->compile != NULL) {
		child_pred_args = predicate->compile(((struct dt_args_t*)datatype_args)->child_dt_args, &argsizes[1], &arguments[1], argc - 1);
		if (child_pred_args == NULL)
			return NULL;
	} else {
		child_pred_args = NULL;
	}

	pred_args = malloc(sizeof(*pred_args));
	pred_args->predicate = predicate;
	pred_args->child_pred_args = child_pred_args;
	return pred_args;
}

API_FILELOCAL int predicate_any_execute(char *data, size_t size, void *predicate_arg, void **internal) {
	uint32_t i, arr_items;
	size_t item_size;
	char *ptr;
	predicate_t *predicate;
	void *child_pred_args;

	ptr = data + sizeof(arr_items);

	arr_items = *((uint32_t*)data);
	for (i = 0; i < arr_items; i++) {
		item_size = *((size_t*)ptr);
		ptr += sizeof(item_size);

		predicate = ((struct pred_args_t*)predicate_arg)->predicate;
		child_pred_args = ((struct pred_args_t*)predicate_arg)->child_pred_args;

		/* Once we get a hit, we bail out with success. */
		if (predicate->execute(ptr, item_size, child_pred_args, internal))
			return 1;
		ptr += item_size;
	}

	return 0;
}

API_FILELOCAL void predicate_nary_release(void *predicate_arg) {
	predicate_t *predicate;
	void *child_pred_args;

	predicate = ((struct pred_args_t*)predicate_arg)->predicate;
	child_pred_args = ((struct pred_args_t*)predicate_arg)->child_pred_args;

	if (predicate->destroy != NULL)
		predicate->destroy(child_pred_args);
	
	free(predicate_arg);
}

API_FILELOCAL predicate_t predicates[] = {
	{"any", 1, -1, predicate_nary_compile, predicate_any_execute, predicate_nary_release},
/*	{"every", 1, -1, predicate_nary_compile, predicate_every_execute, predicate_nary_release},*/
};


API_LIBLOCAL datatype_t dt_array = {
	"array",

	1, -1,
	dt_arguments_parse, dt_arguments_destroy,

	verify,
	NULL, /* TODO */

	predicates,
};
